package MyMMU

import chisel3._
import chisel3.util._
import MYFIFO._
import MACArraySetUp._
import MACArray._
import MyIO._
import MYAccumulators._


class MyMMU(dataWidth:Int, matrixSize:Int, ddrDataWidth:Int=64, addrWidth:Int=8) extends Module{
	val io = IO(new Bundle{
		val vectorIn = Input(UInt((matrixSize*matrixSize*dataWidth).W))//to setup module
		val accumRaddr = Input(UInt(addrWidth.W))//to accum module
		val mmu2setup = new Setup_Accu(addrWidth)//to setup module
		val switchW = Input(Bool())//to setup moduel
		val ddrData = Input(UInt((ddrDataWidth*8).W))//to FIFO
		val ddrValid = Input(Bool())//to FIFO

		val done  = Output(Bool())
		val accOut = Output(Vec(matrixSize, SInt(32.W)))
	})

	val logn1 = log2Up(matrixSize + 2+1)
	println("logn1 is: %d", logn1)
	val logn = log2Up(matrixSize+2)
	println("logn is: %d", logn)

	val totalWait = matrixSize + 1

	val programming = RegInit(false.B)//if true, programming new weights now
	val waiting = WireDefault(false.B)//if true, a switch operation is underway, and need to wait

	val weightsWait = RegInit(totalWait.U(logn1.W))//counts cycles since last weigth push
	val weightsCount = RegInit(0.U(logn.W))//counts cycles of current weight push

	val startup = RegNext(true.B, false.B)//startup will only be false while rst
	val weightsWe = WireDefault(false.B)//
	val doneProgramming = WireDefault(false.B)
	val firstTile = RegInit(false.B)

	//FIFO
	/*
class MYFIFO(matSize:Int, memDataWidth:Int=64, dataWidth:Int=8) extends Module{
	val io = IO(new Bundle{
		val memData = Input(UInt((memDataWidth*8).W))
		val memValid = Input(Bool())
		val advance_fifo = Input(Bool())

		val tile = Output(Vec(matSize, UInt((dataWidth*matSize).W)))
		val ready = Output(Bool())
		val full = Output(Bool())
	})
	*/

	val myFifo = Module(new MYFIFO(matrixSize, ddrDataWidth))
	//input
	myFifo.io.memData := io.ddrData
	myFifo.io.memValid := io.ddrValid
	myFifo.io.advance_fifo := doneProgramming
	//output

	//macarray setup//////////////////////////////////////////////////////
	/*
	class MACArraySetUp(matSize:Int, dataWidth:Int=8, addrWidth:Int=8) extends Module{
	val io = IO(new Bundle{
		val dataIn = Input(UInt((matSize*matSize*dataWidth).W))
		val mmu2setup = new Setup_Accu(addrWidth)
		val switch = Input(Bool())

		val nextRow = Output(Vec(matSize, SInt(dataWidth.W)))
		val switchOut = Output(Vec(matSize, Bool()))//switch signals for MACArray
		val setup2accu = Flipped(new Setup_Accu(addrWidth))
	})
	*/
	val macArraySetup = Module(new MACArraySetUp(matrixSize*1))
	//input
	macArraySetup.io.dataIn := io.vectorIn
	macArraySetup.io.mmu2setup <> io.mmu2setup
	macArraySetup.io.switch := io.switchW
	//output

	//MACArray///////////////////////////////////////////////////////////
	/*
	class MACArray(matrixSize:Int=5, dataWidth:Int=8) extends Module {
    val io = IO(new Bundle{
        val dataIn = Input(Vec(matrixSize, SInt(dataWidth.W)))//the data input to the leftmost column
        val switchw = Input(Vec(matrixSize, Bool()))          //the switchw input to the leftmost  clolumn
        val weightIn = Input(Vec(matrixSize, UInt((dataWidth*matrixSize).W)))//the weight input for the entire matrix
        //val weightWe = Input(Vec(matrixSize, Bool()))
        val weightWe = Input(Bool())                          //when active, start to programming the weight for preparation

        val dataOut = Output(Vec(matrixSize, SInt(32.W)))         //the output data for every column
    })
	*/
	val macArray = Module(new MACArray(matrixSize, dataWidth))
	macArray.io.dataIn := macArraySetup.io.nextRow
	macArray.io.switchw := macArraySetup.io.switchOut
	macArray.io.weightIn := myFifo.io.tile
	macArray.io.weightWe := weightsWe

	//accumulators///////////////////////////////////////////////////
	/*
	class MYAccumulators(addrWidth:Int, matrixSize:Int) extends Module{
	val io = IO(new Bundle{
		val accumsIn = new Setup_Accu(addrWidth)
		val datasIn = Input(Vec(matrixSize, SInt(32.W)))
		val raddr = Input(UInt(addrWidth.W))

		val datasOut = Output(Vec(matrixSize, SInt(32.W)))
		val done = Output(Bool())
	})
	*/
	val accumulators = Module(new MYAccumulators(addrWidth, matrixSize))
	//input
	accumulators.io.accumsIn := macArraySetup.io.setup2accu
	accumulators.io.datasIn := macArray.io.dataOut
	accumulators.io.raddr := io.accumRaddr
	//output
	io.done := accumulators.io.done
	io.accOut := accumulators.io.datasOut

	//val totalWait = matrixSize + 1
	waiting := weightsWait =/= totalWait.U//if waiting is true, need to wait

	when(~startup){//启动时，将weightsWait设置为totalWait，这样waiting就为低
		weightsWait := totalWait.U
	}.elsewhen(waiting){//当waiting为高时，+1
		weightsWait := weightsWait + 1.U
	}.elsewhen(~firstTile && myFifo.io.ready){//如果是第一次更新权重，并且fifo中的权重已经准备完毕
		weightsWait := totalWait.U//因为是第一次更新权重,不需要等待switch传播,所以直接将weightsWait设置为totalwait，防止下个时钟waiting为高
		programming := true.B//将programming设置为true，下个始终开始传播权重
		weightsCount := 0.U
		firstTile   := true.B//更改flag，下次不是第一次更新权重了
	}.elsewhen(macArraySetup.io.switchOut(0)){//如果不是第一次更新权重，则当MMU的输入io.switchW为高一个时钟周期之后
		weightsWait := 0.U//将该计数器设置为0,为switch信号传播的初始计数器值，以便于下个时钟进入waiting状态，进行计数
		programming := true.B//将programming设置为1,以便于waiting状态结束时，进入programming状态
		weightsCount := 0.U//将该计数器设置为0,为权重传播的初始计数器值
	}.elsewhen(programming){//当开始传播权重
		when(weightsCount===(matrixSize-1).U){//计数打到matrixSize-1个时钟后，停止计数；这里隐含了将weightsWe拉低
			programming := false.B
			doneProgramming := true.B//该信号链接到FIFO模块的advance_fifo输入，使得FIFO模块可以清空当前输出
		} .otherwise{//否则继续计数，并保持weightsWe为高
			weightsCount := weightsCount + 1.U
			weightsWe := true.B
		}
	}
}


object Main extends App {
	println("Generating MyMMU hardware")
	emitVerilog(new MyMMU(8,3), Array("--target-dir", "generated"))
}